/*******************************************************************************
 * Copyright (c) 2002, 2003 David Corbin and others. All rights reserved. This
 * program and the accompanying materials are made available under the terms of
 * the Common Public License v0.5 which accompanies this distribution, and is
 * available at http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors: David Corbin - Initial implementation. Boris Pruessmann - Some
 * cleanup Rouven Froeleke - Base implementation of moveFolder
 ******************************************************************************/
package net.sourceforge.perforce.core.internal;

import com.google.common.collect.Lists;

import net.sourceforge.perforce.core.IP4TeamProvider;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.PerforceSynchronizer;
import net.sourceforge.perforce.core.api.IPerforceServer;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.team.IMoveDeleteHook;
import org.eclipse.core.resources.team.IResourceTree;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.team.core.RepositoryProvider;

import java.io.File;
import java.util.List;


/**
 * Implements IMoveDeleteHook, so refactoring of classes in source control will
 * work correctly.
 * 
 * @see org.eclipse.core.resources.team.IMoveDeleteHook
 * 
 * @author <a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
 * @author <a href="mailto:bpruessm@users.sourceforge.net">Boris Pruessmann</a>
 * @version $Revision: 488 $
 */
class MoveDeleteHook implements IMoveDeleteHook {
  // ---------------------------------------------------------------------------
  // Private Variables

  /** Stores the Team provider to which the hook belongs. */
  private IP4TeamProvider provider;

  // -------------------------------------------------------------------------
  // Public Constructors

  /**
   * Constructor for MoveDeleteHook.
   * 
   * @param provider the provider.
   */
  public MoveDeleteHook(IP4TeamProvider provider) {
    this.provider = provider;
  }

  //----------------------------------------------------------------------------
  // -- Public Methods

  /** @see IMoveDeleteHook#deleteFile(IResourceTree, IFile, int, IProgressMonitor) */
  public boolean deleteFile(IResourceTree tree, IFile file, int flags, IProgressMonitor monitor) {
    if (!provider.hasRemote(file)) {
      return false;
    }

    monitor = Policy.monitorFor(monitor);
    monitor.beginTask("", 1500);
    monitor.subTask("");

    // If the local file is opened for ... we have to revert it
    IResource[] resources = new IResource[] {file};
    if (provider.isCheckedOut(file)) {
      try {
        IProgressMonitor subMonitor =
            Policy.subMonitorFor(monitor, 500, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL);

        provider.revert(resources, IResource.DEPTH_ONE, null);
      } catch (PerforceException e) {
        tree.failed(e.getStatus());
        /*
         * this method declines to assume responsibility for this operation,
         * indicated by returning false
         */
        return false;
      }
    } else {
      monitor.worked(500);
    }

    if (PerforceCorePlugin.getPlugin().getDeleteAffectsChangelist()) {
      /*
       * If the file is now under perforce control we have to open it for
       * delete.
       */
      if (provider.hasRemote(file)) {
        try {
          boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0;
          /*
           * Add the file to the local history if requested by the user.
           */
          if (keepHistory) {
            tree.addToLocalHistory(file);
          }
          monitor.worked(500);

          IProgressMonitor subMonitor =
              Policy.subMonitorFor(monitor, 500, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL);

          IStatus stat = provider.openForDelete(resources, IResource.DEPTH_ONE, subMonitor);

          if (stat.isOK()) return true;

          tree.failed(stat);
          return false;
        } catch (PerforceException e) {
          tree.failed(e.getStatus());
          return false;
        } finally {
          monitor.done();
        }
      } else {
        monitor.worked(1000);
      }
    }

    tree.standardDeleteFile(file, flags, Policy.subMonitorFor(monitor, 1000));
    return true;
  }

  /**
   * @see IMoveDeleteHook#deleteFolder(IResourceTree, IFolder, int,
   *      IProgressMonitor)
   */
  public boolean deleteFolder(IResourceTree tree, IFolder folder, int flags,
      IProgressMonitor monitor) {
    if (!provider.hasRemote(folder)) {
      return false;
    }

    try {
      final List<IFile> resourceList = Lists.newArrayList();
      folder.accept(new IResourceVisitor() {
        public boolean visit(IResource resource) {
          if (resource.getType() == IResource.FILE) {
            resourceList.add((IFile) resource);
          }
          return true;
        }
      });

      monitor = Policy.monitorFor(monitor);
      monitor.beginTask("", resourceList.size() * 1000 + 1000);
      monitor.subTask("");

      // We lock the PerforceSynchronizer during the delete process to avoid
      // cache commits
      try {
        PerforceSynchronizer.getInstance().beginOperation(null);

        for (IFile file : resourceList) {
          IProgressMonitor subMonitor = Policy.subMonitorFor(monitor, 1000);
          deleteFile(tree, file, flags, subMonitor);
        }
      } finally {
        PerforceSynchronizer.getInstance().endOperation(null);
      }

      monitor.done();

      tree.standardDeleteFolder(folder, flags, monitor);
    } catch (PerforceException e) {
      tree.failed(e.getStatus());
    } catch (CoreException e) {
      tree.failed(e.getStatus());
    }

    return false;
  }

  /**
   * @see IMoveDeleteHook#deleteProject(IResourceTree, IProject, int,
   *      IProgressMonitor)
   */
  public boolean deleteProject(IResourceTree tree, IProject project, int updateFlags,
      IProgressMonitor monitor) {
    return false;
  }

  /**
   * @see IMoveDeleteHook#moveFile(IResourceTree, IFile, IFile, int,
   *      IProgressMonitor)
   */
  public boolean moveFile(IResourceTree tree, IFile source, IFile destination, int updateFlags,
      IProgressMonitor monitor) {
    refreshState(new IResource[] {source, destination}, null);
    if (!provider.hasRemote(source)) {
      return false;
    }

    monitor.beginTask("", 1000); //$NON-NLS-1$
    monitor.subTask(source.getName());
    try {
      final IPerforceServer server = ((P4TeamProvider) provider).getPerforceServer();
      if (server.moveFile(tree, source, destination, updateFlags, monitor)) {
        tree.movedFile(source, destination);
        return true;
      } else {
        return false;
      }
    } finally {
      refreshState(new IResource[] {source, destination}, null);
      PerforceCorePlugin.getPlugin().broadcastChangelistChanges();
      monitor.done();
    }
  }

  /**
   * @see IMoveDeleteHook#moveFolder(IResourceTree, IFolder, IFolder, int,
   *      IProgressMonitor)
   */
  public boolean moveFolder(IResourceTree tree, IFolder source, IFolder destination,
      int updateFlags, IProgressMonitor monitor) {
    try {
      PerforceSynchronizer.getInstance().beginOperation(null);

      refreshState(new IResource[] {source, destination}, null);
      if (!provider.hasRemote(source)) {
        return false;
      }

      monitor = moveFolderOperation(tree, source, destination, updateFlags, monitor);

      return true;
    } catch (CoreException e) {
      tree.failed(e.getStatus());
    } finally {
      try {
        PerforceSynchronizer.getInstance().endOperation(monitor);
      } catch (PerforceException e) {
        PerforceCorePlugin.log(e.getStatus());
      }
    }
    return false;
  }

  private IProgressMonitor moveFolderOperation(IResourceTree tree, IFolder source,
      IFolder destination, int updateFlags, IProgressMonitor monitor) throws CoreException {
    try {
      final List<IFile> resourceList = Lists.newArrayList();
      source.accept(new IResourceVisitor() {
        public boolean visit(IResource resource) {
          if (resource.getType() == IResource.FILE) {
            resourceList.add((IFile) resource);
          }
          return true;
        }
      });

      monitor = Policy.monitorFor(monitor);
      monitor.beginTask("", resourceList.size() * 1000 + 1000); //$NON-NLS-1$
      monitor.setTaskName(""); //$NON-NLS-1$

      IPath sourcePath = source.getProjectRelativePath();

      final IPerforceServer server = ((P4TeamProvider) provider).getPerforceServer();
      boolean ok = true;
      for (IFile file : resourceList) {
        IPath filePath = file.getProjectRelativePath();

        int matchingSegments = filePath.matchingFirstSegments(sourcePath);
        IPath delta = filePath.removeFirstSegments(matchingSegments);

        IFile destinationFile = destination.getFile(delta);
        createFolder((IFolder) destinationFile.getParent());

        IProgressMonitor subMonitor = Policy.subMonitorFor(monitor, 1000);

        if (!server.moveFile(tree, file, destinationFile, updateFlags, monitor)) {
          ok = false;
        }
      }

      if (ok) {
        deleteDirectory(source);
        tree.movedFolderSubtree(source, destination);
      }
    } finally {
      refreshState(new IResource[] {source, destination}, null);
      PerforceCorePlugin.getPlugin().broadcastChangelistChanges();

      IProgressMonitor subMonitor = Policy.subMonitorFor(monitor, 1000);
      monitor.done();
      monitor = subMonitor;
    }
    return monitor;
  }

  /**
   * @see IMoveDeleteHook#moveProject(IResourceTree, IProject,
   *      IProjectDescription, int, IProgressMonitor)
   */
  public boolean moveProject(IResourceTree tree, IProject source, IProjectDescription description,
      int updateFlags, IProgressMonitor monitor) {
    return false;
  }

  //----------------------------------------------------------------------------
  // - Private Methods

  /**
   * Called to create the folder.
   * 
   * @param folderHandle the folder handle.
   */
  private void createFolder(IFolder folderHandle) {
    File folder = new File(folderHandle.getLocation().toOSString());
    folder.mkdirs();
  }

  private void deleteDirectory(IFolder folderHandle) {
    File folder = new File(folderHandle.getLocation().toOSString());
    folder.delete();
  }

  /** @see P4TeamProvider#refreshState(IResource[], int, IProgressMonitor) */
  private void refreshState(IResource[] resources, IProgressMonitor monitor) {
    P4TeamProvider provider =
        (P4TeamProvider) RepositoryProvider.getProvider(resources[0].getProject());

    provider.refreshState(resources, IResource.DEPTH_INFINITE, monitor);
  }
}
